﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

using DITCharts.Shapes;
using DITCharts.Util;
using System.Windows.Media.Animation;
using DITCharts.DITCharts.PieChart;
using TestStarter.Model;


namespace DITCharts.Controls.PieChart
{
    /// <summary>
    /// Renders a bound dataset as a pie chart
    /// </summary>
    public partial class PiePlotter : UserControl
    {

        #region dependency properties

        /// <summary>
        /// The property of the bound object that will be plotted
        /// </summary>
        public ObservableCollection<ChartData> DataSourceItems
        {
            get { return PieChartLayout.GetDataSourceItems(this); }
            set { PieChartLayout.SetDataSourceItems(this, value); }
        }

        /// <summary>
        /// The property of the bound object that will be plotted
        /// </summary>
        public String ValueMember
        {
            get { return PieChartLayout.GetValueMember(this); }
            set { PieChartLayout.SetValueMember(this, value); }
        }

        /// <summary>
        /// The property of the bound object that will be plotted
        /// </summary>
        public String TitleMember
        {
            get { return PieChartLayout.GetTitleMember(this); }
            set { PieChartLayout.SetTitleMember(this, value); }
        }

        /// <summary>
        /// A class which selects a color based on the item being rendered.
        /// </summary>
        public IColorSelector ColorSelector
        {
            get { return PieChartLayout.GetColorSelector(this); }
            set { PieChartLayout.SetColorSelector(this, value); }
        }


        /// <summary>
        /// The size of the hole in the centre of circle (as a percentage)
        /// </summary>
        public double HoleSize
        {
            get { return (double)GetValue(HoleSizeProperty); }
            set
            {
                SetValue(HoleSizeProperty, value);
                ConstructPiePieces();
            }
        }

        public static readonly DependencyProperty HoleSizeProperty =
                       DependencyProperty.Register("HoleSize", typeof(double), typeof(PiePlotter), new UIPropertyMetadata(0.0));

       
        #endregion


        /// <summary>
        /// A list which contains the current piece pieces, where the piece index
        /// is the same as the index of the item within the collection view which 
        /// it represents.
        /// </summary>
        private List<PiePiece> piePieces = new List<PiePiece>();

        public PiePlotter()
        {
            // register any dependency property change handlers
            DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(PieChartLayout.DataSourceItemsProperty, typeof(PiePlotter));
            dpd.AddValueChanged(this, PlottedPropertyChanged);

            DependencyPropertyDescriptor dpd1 = DependencyPropertyDescriptor.FromProperty(PieChartLayout.ValueMemberProperty, typeof(PiePlotter));
            dpd1.AddValueChanged(this, PlottedPropertyChanged);

            DependencyPropertyDescriptor dpd2 = DependencyPropertyDescriptor.FromProperty(PieChartLayout.TitleMemberProperty, typeof(PiePlotter));
            dpd2.AddValueChanged(this, PlottedPropertyChanged);

            InitializeComponent();

            this.DataContextChanged += new DependencyPropertyChangedEventHandler(DataContextChangedHandler);
        }

        #region property change handlers

        /// <summary>
        /// Handle changes in the datacontext. When a change occurs handlers are registered for events which
        /// occur when the collection changes or any items within teh collection change.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void DataContextChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
        {
            // handle the events that occur when the bound collection changes
            if (this.DataContext is INotifyCollectionChanged)
            {
                INotifyCollectionChanged observable = (INotifyCollectionChanged)DataSourceItems;
                observable.CollectionChanged += new NotifyCollectionChangedEventHandler(BoundCollectionChanged);
            }

                // handle the selection change events
                CollectionView collectionView = (CollectionView)CollectionViewSource.GetDefaultView(DataSourceItems);
                collectionView.CurrentChanged += new EventHandler(CollectionViewCurrentChanged);
                collectionView.CurrentChanging += new CurrentChangingEventHandler(CollectionViewCurrentChanging);

            ConstructPiePieces();
            ObserveBoundCollectionChanges();
        }

        /// <summary>
        /// Handles changes to the PlottedProperty property.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PlottedPropertyChanged(object sender, EventArgs e)
        {
            ConstructPiePieces();
        }

        #endregion

        #region event handlers

        /// <summary>
        /// Handles the MouseUp event from the individual Pie Pieces
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PiePieceMouseUp(object sender, MouseButtonEventArgs e)
        {
            CollectionView collectionView = (CollectionView)CollectionViewSource.GetDefaultView(DataSourceItems);
            if (collectionView == null)
                return;

            PiePiece piece = sender as PiePiece;
            if (piece == null)
                return;

            // select the item which this pie piece represents
            int index = (int)piece.Tag;
            collectionView.MoveCurrentToPosition(index);
        }

        /// <summary>
        /// Handles the event which occurs when the selected item is about to change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CollectionViewCurrentChanging(object sender, CurrentChangingEventArgs e)
        {
            CollectionView collectionView = (CollectionView)sender;

            if (collectionView != null && collectionView.CurrentPosition >= 0 && collectionView.CurrentPosition < piePieces.Count && piePieces.Count != 1)
            {
                PiePiece piece = piePieces[collectionView.CurrentPosition];

                DoubleAnimation a = new DoubleAnimation();
                a.To = 0;
                a.Duration = new Duration(TimeSpan.FromMilliseconds(200));

                piece.BeginAnimation(PiePiece.PushOutProperty, a);
            }
        }

        /// <summary>
        /// Handles the event which occurs when the selected item has changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CollectionViewCurrentChanged(object sender, EventArgs e)
        {
            CollectionView collectionView = (CollectionView)sender;

            if (collectionView != null && collectionView.CurrentPosition >= 0 && collectionView.CurrentPosition < piePieces.Count && piePieces.Count != 1)
            {
                PiePiece piece = piePieces[collectionView.CurrentPosition];

                DoubleAnimation a = new DoubleAnimation();
                a.To = 10;
                a.Duration = new Duration(TimeSpan.FromMilliseconds(200));

                piece.BeginAnimation(PiePiece.PushOutProperty, a);
            }

            
        }

        /// <summary>
        /// Handles events which are raised when the bound collection changes (i.e. items added/removed)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BoundCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ConstructPiePieces();
            ObserveBoundCollectionChanges();
        }

        /// <summary>
        /// Iterates over the items inthe bound collection, adding handlers for PropertyChanged events
        /// </summary>
        private void ObserveBoundCollectionChanges()
        {
            if (DataSourceItems != null)
            {
                CollectionView myCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(DataSourceItems);

                foreach (object item in myCollectionView)
                {
                    if (item is INotifyPropertyChanged)
                    {
                        INotifyPropertyChanged observable = (INotifyPropertyChanged)item;
                        observable.PropertyChanged += new PropertyChangedEventHandler(ItemPropertyChanged);
                    }
                }
            }
        }

        
        /// <summary>
        /// Handles events which occur when the properties of bound items change.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // if the property which this pie chart represents has changed, re-construct the pie
            if (e.PropertyName.Equals(ValueMember))
            {
                ConstructPiePieces();
            }
        }

        #endregion

        private double GetPlottedPropertyValue(object item)
        {
            PropertyDescriptorCollection filterPropDesc = TypeDescriptor.GetProperties(item);
            object itemValue = filterPropDesc[ValueMember].GetValue(item);

            //TODO possibel type conversion?

            return (double)itemValue;
        }

        /// <summary>
        /// Constructs pie pieces and adds them to the visual tree for this control's canvas
        /// </summary>
        private void ConstructPiePieces()
        {
            CollectionView myCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(DataSourceItems);
            if (myCollectionView == null)
                return;

            double halfWidth = this.Width / 2;
            double innerRadius = halfWidth * HoleSize;            

            // compute the total for the property which is being plotted
            double total = 0;
            foreach (Object item in myCollectionView)
            {
                total += GetPlottedPropertyValue(item);
            }
            
            // add the pie pieces
            canvas.Children.Clear();
            piePieces.Clear();
                        
            double accumulativeAngle=0;
            foreach (Object item in myCollectionView)
            {
                bool selectedItem = item == myCollectionView.CurrentItem;

                double wedgeAngle = GetPlottedPropertyValue(item) * 360 / total;

                if (wedgeAngle == 360.0)
                {
                    wedgeAngle = 359.99;
                    selectedItem = false;
                }

                if (wedgeAngle != 0)
                {
                    PiePiece piece = new PiePiece()
                        {
                            Radius = halfWidth,
                            InnerRadius = innerRadius,
                            CentreX = halfWidth,
                            CentreY = halfWidth,
                            PushOut = (selectedItem ? 10.0 : 0),
                            WedgeAngle = wedgeAngle,
                            PieceValue = GetPlottedPropertyValue(item),
                            RotationAngle = accumulativeAngle,
                            Fill = getBrush(myCollectionView,item),
                            // record the index of the item which this pie slice represents
                            Tag = myCollectionView.IndexOf(item),
                            ToolTip = new ToolTip()
                        };

                    piece.ToolTipOpening += new ToolTipEventHandler(PiePieceToolTipOpening);
                    piece.MouseUp += new MouseButtonEventHandler(PiePieceMouseUp);

                    myCollectionView.CurrentChanged += new EventHandler(CollectionViewCurrentChanged);
                    myCollectionView.CurrentChanging += new CurrentChangingEventHandler(CollectionViewCurrentChanging);

                    piePieces.Add(piece);
                    canvas.Children.Insert(0, piece);

                    accumulativeAngle += wedgeAngle;
                }
            }
        }

        private Brush getBrush(CollectionView myCollectionView, Object item)
        {
            if(ColorSelector != null )
            {
                return ColorSelector.SelectBrush(item, myCollectionView.IndexOf(item));
            }
            else if (myCollectionView.IndexOf(item) == 0)
            {
                return Brushes.Green;
            }
            else if (myCollectionView.IndexOf(item) == 1)
            {
                return Brushes.Red;
            }
            else if (myCollectionView.IndexOf(item) == 2)
            {
                return Brushes.Yellow;
            }
            else
            {
                return Brushes.Green;
            }
        }

        /// <summary>
        /// Handles the event which occurs just before a pie piece tooltip opens
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PiePieceToolTipOpening(object sender, ToolTipEventArgs e)
        {
            PiePiece piece = (PiePiece)sender;

            CollectionView collectionView = (CollectionView)CollectionViewSource.GetDefaultView(DataSourceItems);
            if (collectionView == null)
                return;
                       
            // select the item which this pie piece represents
            int index = (int)piece.Tag;
            if (piece.ToolTip != null)
            {
                ToolTip tip = (ToolTip)piece.ToolTip;
                tip.DataContext = collectionView.GetItemAt(index);
            }         
        }

    }
}
